import asyncio
import base64
import datetime
import json
import logging
import random
import sys
import time
import urllib
from collections import defaultdict
from datetime import timedelta

import aiohttp
import aioredis
import pymongo
import tornado
from faker import Faker
from tornado import gen

from baseHandler import Application, PayHandler, init_logger, http_result
from util.jd_jiayouka import get_weixin, youka_query, reget_wx

lock = asyncio.Lock()
RECHANGE_QUEUE = defaultdict(asyncio.Queue)

INTEVAL = 0.2
QUERY_TIME = 60 * 30


class CmccHandler(PayHandler):
    @gen.coroutine
    def post(self):
        logger.info(self.request.path)
        db = self.application.db
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        data = self.decrypt()
        if not data:
            return self.write({"message": "解密失败", "ret": -1})
        data = json.loads(data)
        logger.info(data)
        arguments = ["ordernumber", "amount", "cardno", "tel_number"]
        for arg in arguments:
            if arg not in data:
                return self.write({"ret": -1, "message": "缺少%s" % arg})
        logger.info("油卡充值%s|%s|%s", data['ordernumber'], data['cardno'], data['amount'])
        res = self.check_ordernumber(data)
        if res:
            return self.write(res)

        start_time = time.time()
        obj = {
            "cardno": data["cardno"],
            "tel_number": data["tel_number"],
            "ordernumber": data["ordernumber"],
            "time": start_time,
            "amount": data["amount"]
        }

        data['time'] = start_time
        data["orderdate"] = datetime.datetime.now().strftime('%Y%m%d')
        data["merchantid"] = self.merchantid
        data['tel_type'] = 'youka'
        data['orderstate'] = '2'
        try:
            yield db.rechange.insert_one(data)
            # self.application.redis.lpush('youka_recharge', json.dumps({"ordernumber": data['ordernumber']}))
        except pymongo.errors.DuplicateKeyError:
            return self.write({"ret": -1, "message": "重复的订单号"})

        if data["amount"] in ('200', '500', '1000', '2000'):
            queue_name = "%s_%s" % (self.merchantid, data["amount"])
            yield from RECHANGE_QUEUE[queue_name].put(obj)
            logger.info(RECHANGE_QUEUE[queue_name].qsize())
        else:
            return self.write({"message": "不支持的金额", "ret": -1})

        res = {"orderstate": '2', "message": "订单提交成功", }
        return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
        return_obj.update({"ret": 0, "message": "success"})
        self.write(return_obj)
        self.finish()


class WxHandler(PayHandler):
    @gen.coroutine
    def post(self):
        db = self.application.db
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        data = self.decrypt()
        if not data:
            return self.write({"message": "解密失败", "ret": -1})
        data = json.loads(data)
        logger.info(data)
        arguments = ["payorderno", "amount"]
        for arg in arguments:
            if arg not in data:
                return self.write({"ret": -1, "message": "缺少%s" % arg})

        obj = yield db.rechange.find_one({"payorderno": data["payorderno"]})

        if obj:
            logger.info(obj)
            return self.write({"message": "订单号重复", "ret": -1})
        logger.info("支付订单%s|%s|%s", self.merchantid, data["payorderno"], data['amount'])
        supplier = self.merchant_info.get("supplier")
        if data["amount"] not in ('200', '500', '1000', '2000'):
            return self.write({"message": "不支持的金额", "ret": -1})
        if not supplier:
            return self.write({"message": "无有效供应商", "ret": -1})
        is_new = False
        while True:
            try:
                queue_name = "%s_%s" % (supplier, data["amount"])
                logger.info(queue_name)
                obj = yield self.application.redis.rpop(queue_name)
                logger.info(obj)
                if not obj:
                    obj = RECHANGE_QUEUE[queue_name].get_nowait()
                    if not obj:
                        return self.write({'ret': -1, 'message': "无有效订单"})
                    if time.time() - obj["time"] > 60 * 9:
                        continue
                    is_new = True
                    yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                 {"$set": {
                                                     "orderstate": "8"
                                                 }})
                else:
                    obj = json.loads(obj.decode())
                    # find in recharge
                    recharge_obj = yield db.rechange.find_one({"ordernumber": obj["ordernumber"]})
                    if recharge_obj["payorderno"]:
                        continue
                    if time.time() - recharge_obj["time"] > 60 * 20:
                        continue
                    if time.time() - obj["time"] < 60:
                        continue
                try:
                    if is_new:
                        future = get_weixin(obj["cardno"], obj["tel_number"], obj['amount'], self.application.redis)
                    else:
                        if not obj.get('orderid'):
                            pay_url = obj.get('pay_url')
                        else:
                            pay_url = None
                        future = reget_wx(obj['jd_id'], obj.get('orderid'), obj.get('pay_sign'), self.application.redis, pay_url)
                    result = yield gen.with_timeout(timedelta(seconds=40), future)
                except Exception as e:
                    logger.info("%s取单失败", obj["ordernumber"])
                    logger.error(e, exc_info=True)
                    result = {"code": "1", "message": "取二维码失败"}

                logger.info(result)
                if result.get("code") in ('0', '8'):
                    orderstate = '8'
                    wx_url = result.get("wx_url")
                    order_id = result.get("orderId")
                    pay_url = result.get("pay_url")
                    pay_sign = result.get("paySign")
                    jd_id = result.get("jd_id")
                    pay_amount = result.get("pay_amount")
                    # if is_new:
                    yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                 {"$set":
                                                     {
                                                         "orderstate": orderstate,
                                                         "orderid": order_id,
                                                         "message": result.get("message"),
                                                         "pay_url": pay_url,
                                                         "pay_sign": pay_sign,
                                                         "jd_id": jd_id,
                                                         "pay_amount": pay_amount,
                                                         "payorderno": data["payorderno"]
                                                     }
                                                 })
                    yield db.payorder.insert_one({
                        "time": time.time(),
                        "orderdate": datetime.datetime.now().strftime('%Y%m%d'),
                        "merchantuid": self.merchantid,
                        "ordernumber": obj["ordernumber"],
                        "payorderno": data["payorderno"],
                        "orderstate": orderstate,
                        "pay_url": pay_url or obj.get("pay_url"),
                        "orderid": order_id,
                        "pay_backurl": data.get("backurl"),
                        "pay_sign": pay_sign,
                        "jd_id": jd_id,
                        "pay_amount": data["amount"]
                    })
                    self.application.redis.lpush('youka_recharge', json.dumps({"payorderno": data['payorderno']}))
                else:
                    orderstate = '1'
                    wx_url = ""
                    order_id = ""
                    if is_new:
                        yield db.rechange.update_one({"ordernumber": obj["ordernumber"]},
                                                     {"$set": {"orderstate": orderstate,
                                                               "message": result.get("message")}}
                                                     )
                res = {"orderstate": orderstate, "message": result.get("message"), 'wx_url': wx_url,
                       "orderid": order_id}

                return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
                return_obj.update({"ret": 0, "message": "success"})
                self.write(return_obj)
                self.finish()
                return
            except asyncio.QueueEmpty:
                return self.write({'ret': -1, 'message': "无有效订单"})


async def query(query_obj, db, redis):
    now = time.time()

    if "ordernumber" in query_obj:
        obj = await db.rechange.find_one({"ordernumber": query_obj["ordernumber"]})
    else:
        obj = await db.payorder.find_one({"payorderno": query_obj["payorderno"]})
    extra = {}
    if not obj:
        orderstate = "3"
        message = "订单号不存在"
        return orderstate, message, extra

    orderstate = obj.get("orderstate", "2")
    message = ""
    if orderstate in ("0", "1"):
        message = obj.get("message", "")
        extra = {
            # "tel_number": obj["tel_number"],
            "orderid": obj.get("orderid", "")
        }
    else:
        async with lock:
            query_time = obj.get("query_time") or obj["time"]
            if time.time() - query_time < 15:
                message = ""
                return orderstate, message, extra

            query_time = time.time()
            if "ordernumber" in query_obj:
                await db.rechange.update_one(query_obj, {"$set": {"query_time": query_time}})

        if obj.get('orderid'):
            res = await youka_query(obj["jd_id"], obj['orderid'], redis)
            orderstate = res.get("orderstate")
            message = res.get("message")
            extra = {
                "orderid": obj.get("orderid", ""),
            }
        elif obj.get("pay_url"):
            orderstate = "8"
            message = "等待获取二维码"
            extra = {
                "orderid": obj.get("orderid", ""),
            }
        else:
            if time.time() - obj['time'] > 60 * 10 and obj.get('orderstate', '2') == '2':
                orderstate = '1'

        if "ordernumber" in query_obj:
            await db.rechange.update_one({"ordernumber": query_obj["ordernumber"]}, {
                "$set": {"orderstate": orderstate, "query_time": query_time, "message": message}})
        else:
            await db.payorder.update_one({"payorderno": query_obj["payorderno"]}, {
                "$set": {"orderstate": orderstate, "query_time": query_time, "message": message}})

    return orderstate, message, extra


class CmccQuery(PayHandler):
    @gen.coroutine
    def post(self):
        db = self.application.db
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        data = self.decrypt()
        if not data:
            return self.write({"message": "解密失败", "ret": -1})
        data = json.loads(data)

        if data.get("ordernumber"):
            query_obj = {"ordernumber": data["ordernumber"]}
        elif data.get("payorderno"):
            query_obj = {"payorderno": data["payorderno"]}

        else:
            return self.write({"message": "无效参数", "ret": -1})

        # orderstate, message, extra = yield query(query_obj, db, self.application.redis)
        if "ordernumber" in query_obj:
            # obj = yield db.rechange.find_one({"ordernumber": query_obj["ordernumber"]})
            orderstate, message, extra = yield query(query_obj, db, self.application.redis)
        else:
            obj = yield db.payorder.find_one({"payorderno": query_obj["payorderno"]})
            extra = {}
            if not obj:
                orderstate = "3"
                message = "订单号不存在"
            else:
                orderstate = obj.get("orderstate", "2")
                message = ""
                if orderstate in ("0", "1", "8"):
                    message = obj.get("message", "")
                    extra = {
                        # "tel_number": obj["tel_number"],
                        "orderid": obj.get("orderid", "")
                }
        logger.info("查询%s|%s|%s|%s", data.get("ordernumber"), data.get("payorderno"), orderstate, message)
        res = {"orderstate": orderstate, "message": message, **extra}
        return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
        return_obj.update({"ret": 0, "message": "success"})
        self.write(return_obj)


class RegetWeixin(PayHandler):
    @gen.coroutine
    def post(self):
        db = self.application.db
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        data = self.decrypt()
        if not data:
            return self.write({"message": "解密失败", "ret": -1})
        data = json.loads(data)

        if data.get("ordernumber"):
            query_obj = {"ordernumber": data["ordernumber"]}
        elif data.get("payorderno"):
            query_obj = {"payorderno": data["payorderno"]}

        else:
            return self.write({"message": "无效参数", "ret": -1})
        # orderstate, message, extra = yield query(query_obj, db, self.application.redis)
        obj = yield db.rechange.find_one(query_obj)
        if not obj:
            return self.write({"message": "订单不存在", "ret": -1})
        res = yield reget_wx(obj['jd_id'], obj['orderid'], obj['pay_sign'], self.application.redis)
        orderstate = res.get("code", "1")
        wx_url = res.get("wx_url", "")
        logger.info("重新获取qrcode%s|%s|%s|%s", data.get("ordernumber"), data.get("payorderno"), orderstate, wx_url)
        res = {"orderstate": orderstate, "wx_url": wx_url}
        return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
        return_obj.update({"ret": 0, "message": "success"})
        self.write(return_obj)


class QueueHandler(PayHandler):
    @gen.coroutine
    def post(self):
        exited = yield self.merchant_exit()
        if not exited:
            logger.info("商户不存在")
            return self.write(json.dumps({"message": "商户不存在", "ret": -1}))
        if not self.check_ip():
            return self.write(json.dumps({"message": "ip错误", "ret": -1}))
        if not self.check_sign():
            logger.info("签名验证失败")
            return self.write(json.dumps({"message": "签名验证失败", "ret": -1}))
        res = {k: v.qsize() for k, v in RECHANGE_QUEUE.items()}
        logger.info(res)
        return_obj = self.niti_data(res, {"MERCHANTID": self.merchantid})
        return_obj.update({"ret": 0, "message": "success"})
        self.write(return_obj)


class SetTimeHandler(tornado.web.RequestHandler):
    @gen.coroutine
    def get(self, query_time):
        if int(query_time) < (6 * 60):
            return self.write("failed")
        QUERY_TIME = query_time
        return self.write('sucess')


if __name__ == "__main__":
    handlers = [
        (r"/pay/yk_recharge", CmccHandler),
        (r"/pay/yk_wxqrcode", WxHandler),
        # (r"/pay/yk_refresh", RegetWeixin),
        (r"/pay/yk_query", CmccQuery),
        # (r"/pay/queue", QueueHandler),
        # (r"/set_querytime/(\d+)", SetTimeHandler)
    ]
    QUERY_TIME = 60 * 6
    application = Application(handlers, debug=False)
    port = sys.argv[1]
    application.listen(port, address="127.0.0.1")
    logger = init_logger("cmcc", "/app/log/cs_cmcc/jiayou.log")
    logger.info("Run unionpay server ... http://127.0.0.1:%s", port)
    loop = asyncio.get_event_loop()
    application.redis = loop.run_until_complete(
        aioredis.create_redis(address=('localhost', 6379), db=0, loop=loop)
    )
    application.init_with_loop(loop)
    loop.run_forever()
